package rtlmmap

import (
	"math"
	"time"

	"github.com/prometheus/common/log"
)

type FTcoApp struct {
	frame_start_point uint64
	ppm               float64
	dfr               float64
	dftr              float64
	timing            float64
}

type IQType interface {
	ToIQ([]byte) []complex128
	WordLength() uint64
}

type FTco struct {
	mem   *CPMem
	app   *FTcoApp
	sys   SystemConfig
	t     IQType
	Index int `json:"idx"`
}

func (*FTcoApp) Attach(mem *CPMem) AppData {
	return (*FTcoApp)((mem.GetAppData()))
}

func NewFTco(idx int, sys SystemConfig, t IQType) *FTco {
	mem := NewCPMem(idx)
	var n *FTcoApp = nil
	app := n.Attach(mem)
	return &FTco{mem, app.(*FTcoApp), sys, t, idx}
}

func (self *FTco) SetTuner(ppm, dfr float64) {
	(*self.app).ppm = ppm
	(*self.app).dfr = dfr
	(*self.app).timing = ppm
	log.Debug("Tuning FTCO", "PPM", ppm, "dfr", dfr)
	self.mem.Tuner(ppm, dfr)
}

func (self *FTco) SetDf(df float64) {
	dftr := df / self.sys.SampleRate()
	dftr += (*self.app).dftr
	(*self.app).dftr = dftr
	self.mem.SetDf(dftr)
}
func (self *FTco) SetAbsf(df float64) {
	dftr := df / self.sys.SampleRate()
	(*self.app).dftr = dftr
	self.mem.SetDf(dftr)
}

func (self *FTco) GetDf() (df float64) {
	df = self.app.dftr * self.sys.SampleRate()
	return
}
func (self *FTco) SetDt(df float64) {
	ppm := df / self.sys.SampleRate() * 1e6
	ppm += (*self.app).timing
	(*self.app).timing = ppm
	self.mem.SetDt(ppm)
}
func (self *FTco) GetDt() (df float64) {
	df = self.app.timing * self.sys.SampleRate() / 1e6
	return
}
func (self *FTco) GetTuner() (ppm, dfr float64) {
	ppm = self.app.ppm
	dfr = self.app.dfr
	return
}

func (self *FTco) PPMAbs(p float64) {
	log.Debug("Set PPM directly")
	dfr := (*self.app).dfr
	self.SetTuner(p, dfr)
}

func (self *FTco) PPMT(df float64) {
	log.Debug("Set PPM by sample rate")
	ppmf := (*self.app).ppm
	ppm := df / self.sys.SampleRate() * 1e6
	newppm := ppm + ppmf
	dfr := (*self.app).dfr
	self.SetTuner(newppm, dfr)
}

func (self *FTco) PPMF(df float64, timingFirst bool) {
	log.Debug("Set PPM by carrier frequency")
	dfr := (*self.app).dfr
	ppmf := (*self.app).ppm
	if !timingFirst {
		ppm := df / (self.sys.SampleRate() * dfr) * 1e6
		ppmf += ppm
	} else {
		dfrf := df / (ppmf * self.sys.SampleRate() * 1e-6)
		dfr += dfrf
	}
	self.SetTuner(ppmf, dfr)
	// log.Info("Set PPM F return", ppmf, dfr)
}

func (self *FTco) ResetPPM() {
	log.Debug("Resetting PPM")
	self.SetTuner(0.0, self.sys.CenterFrequency()/self.sys.SampleRate())
	self.SetAbsf(0.0)
	self.SetDf(0.0)
}

func (self *FTco) InitPPM() {
	dfr := (*self.app).dfr
	ppmf := (*self.app).ppm
	log.Info("Initializing PPM ", ppmf, dfr)
	if math.Abs(dfr) < 0.5 {
		self.ResetPPM()
	} else {
		self.PPMF(0.0, false)
		self.SetDf(0.0)
	}
}

func (self *FTco) GetNearest(set bool) uint64 {
	r := self.nearFrame(self.Now())
	if set {
		(*self.app).frame_start_point = r
	}
	log.Debug("new fs:0x%016x now:0x%016x", r, self.Now())
	return r
}
func (self *FTco) FrameStart() uint64  { return (*self.app).frame_start_point }
func (self *FTco) FrameLength() uint64 { return self.sys.FrameLength() }
func (self *FTco) Now() uint64         { return uint64(self.mem.WritePoint() / self.t.WordLength()) }
func (self *FTco) FrameStartAdjust(adj int64) uint64 {
	fstart := int64(self.app.frame_start_point)
	fstart += adj
	r := uint64(fstart)
	(*self.app).frame_start_point = r
	return r
}
func (self *FTco) nearFrame(now uint64) uint64 {
	fstart := int64(self.app.frame_start_point)
	fl := int64(self.sys.FrameLength())
	n := (int64(now) - fstart) / fl
	r := fstart + n*fl
	return uint64(r)
}

func (self *FTco) getRfData(offset uint64, length int64) []byte {
	cnt := 0
	for int64(self.Now())-int64(offset) < length {
		time.Sleep(time.Microsecond * 10000)
		cnt++
		if cnt > 1000 {
			log.Errorf("time out, no packet recv, idx:%d, now:%x, off:%x", self.Index, self.Now(), offset, cnt)
			break
		}
	}
	wl := self.t.WordLength()
	rfd := self.mem.GetBuf(offset*wl, int(length)*int(wl))
	return rfd
}

func (self *FTco) GetIQ(offset uint64, length int64) []complex128 {
	rfd := self.getRfData(offset, length)
	return self.t.ToIQ(rfd)
}
